﻿using JESAI.DistributedLock.Internal;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.DistributedLock.ZooKeeper;

/// <summary>
/// 基于ZooKeeper实现的<see cref="IDistributedLock"/>。使用了https://zookeeper.apache.org/doc/r3.1.2/recipes.html中描述的锁配方。
/// </summary>
public sealed partial class ZooKeeperDistributedLock : IInternalDistributedLock<ZooKeeperDistributedLockHandle>
{
    private readonly ZooKeeperSynchronizationHelper _synchronizationHelper;

    /// <summary>
    /// 根据提供的<paramref name="path"/>、<paramref name="connectionString"/>和<paramref name="options"/>构造一个新的锁。
    /// 
    /// 如果指定了<paramref name="assumePathExists"/>，则在获取锁时不会创建节点，释放锁后也不会删除节点（默认为false）。
    /// </summary>
    public ZooKeeperDistributedLock(
        ZooKeeperPath path,
        string connectionString,
        bool assumePathExists = false,
        Action<ZooKeeperDistributedSynchronizationOptionsBuilder>? options = null)
        : this(path, assumePathExists: assumePathExists, connectionString, options)
    {
        if (path == default) { throw new ArgumentNullException(nameof(path)); }
        if (path == ZooKeeperPath.Root) { throw new ArgumentException("Cannot be the root", nameof(path)); }
    }

    /// <summary>
    /// 根据提供的<paramref name="name"/>、<paramref name="connectionString"/>和<paramref name="options"/>构造一个新的锁。
    /// 
    /// 锁的路径将是根目录'/'的父节点。如果<paramref name="name"/>不是有效的节点名称，它将被转换以确保有效性。
    /// </summary>
    public ZooKeeperDistributedLock(string name, string connectionString, Action<ZooKeeperDistributedSynchronizationOptionsBuilder>? options = null)
        : this(ZooKeeperPath.Root, name, connectionString, options)
    {
    }

    /// <summary>
    /// 根据提供的<paramref name="directoryPath"/>、<paramref name="name"/>、<paramref name="connectionString"/>和<paramref name="options"/>构造一个新的锁。
    /// 
    /// 锁的路径将是<paramref name="directoryPath"/>的父节点。如果<paramref name="name"/>不是有效的节点名称，它将被转换以确保有效性。
    /// </summary>
    public ZooKeeperDistributedLock(ZooKeeperPath directoryPath, string name, string connectionString, Action<ZooKeeperDistributedSynchronizationOptionsBuilder>? options = null)
        : this(
              (directoryPath == default ? throw new ArgumentNullException(nameof(directoryPath)) : directoryPath).GetChildNodePathWithSafeName(name),
              assumePathExists: false,
              connectionString,
              options)
    {
    }

    private ZooKeeperDistributedLock(ZooKeeperPath path, bool assumePathExists, string connectionString, Action<ZooKeeperDistributedSynchronizationOptionsBuilder>? optionsBuilder) =>
        this._synchronizationHelper = new ZooKeeperSynchronizationHelper(path, assumePathExists, connectionString, optionsBuilder);

    /// <summary>
    /// 即获取ZooKeeper节点的路径。
    /// </summary>
    public ZooKeeperPath Path => this._synchronizationHelper.Path;

    /// <summary>
    /// 实现 <see cref="IDistributedLock.Name"/>。显式实现以避免与传入名称中包含的前导 "/" 和基目录混淆。
    /// </summary>
    string IDistributedLock.Name => this.Path.ToString();

    async ValueTask<ZooKeeperDistributedLockHandle?> IInternalDistributedLock<ZooKeeperDistributedLockHandle>.InternalTryAcquireAsync(TimeoutValue timeout, CancellationToken cancellationToken)
    {
        var nodeHandle = await this._synchronizationHelper.TryAcquireAsync(
                hasAcquired: state => state.SortedChildren[0].Path == state.EphemeralNodePath,
                waitAsync: async (zooKeeper, state, watcher) =>
                {
                    var ephemeralNodeIndex = Array.FindIndex(state.SortedChildren, t => t.Path == state.EphemeralNodePath);
                    var nextLowestChildNode = state.SortedChildren[ephemeralNodeIndex - 1].Path;
                    // 如果下一个最低的子节点已经消失，那么等待就结束了。否则，将观察者留在该节点上，以便在它更改时通知我们（在此之前我们无法获取锁）
                    return await zooKeeper.existsAsync(nextLowestChildNode, watcher).ConfigureAwait(false) == null;
                },
                timeout,
                cancellationToken,
                nodePrefix: "lock-"
            )
            // 我们在这里被迫使用同步而非异步，因为ZooKeeperNetEx没有同步API
            .AwaitSyncOverAsync()
            .ConfigureAwait(false);

        return nodeHandle != null ? new ZooKeeperDistributedLockHandle(nodeHandle) : null;
    }
}
